/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

/**
 * @author Clinton Begin
 */
//一、特性表示
//mybatis的日志输出具有如下特性：
//（1）日志量随程序执行量增加（例如：xml文件中sql报错，那么一定会有初始化、xml加载等信息出现）；
//（2）日志输出的顺序，是按顺序且隔离的；
public class ErrorContext {

  private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();//（1）使用ThreadLocal<ErrorContext>类变量可以做到在当前线程执行的任何阶段保证只有一个类对象实例。
  // 即使说，只要是处于一个线程，不论走多少层方法栈都没关系，只要使用对应方法将对应阶段的信息记录下来，最终在所有操作完结的使用toString()一下就能将整个过程的信息输出出来。（否则就要将信息存储的类一个接一个方法地以形参方式传入）

  /**
   * 暂存线程上下文
   */
  private ErrorContext stored;
  private String resource;
  private String activity;
  private String object;
  private String message;
  private String sql;
  //... 各个阶段的值存储
  private Throwable cause;

  private ErrorContext() {
  }

  // 单例模式的变种，从当前线程获取线程存储变量（一个）
  public static ErrorContext instance() {//将ErrorContext用ThreadLocal与currentThread绑定
    ErrorContext context = LOCAL.get();
    if (context == null) {
      context = new ErrorContext();
      LOCAL.set(context);
    }
    return context;
  }

  public ErrorContext store() {// 用stored暂存当前errorContext，并且TheadLocal设置新的errorContext
    stored = this;
    LOCAL.set(new ErrorContext());
    return LOCAL.get();
  }

  public ErrorContext recall() {// TheadLocal设置stored缓存的errorContext，然后将stored设置为null
    if (stored != null) {
      LOCAL.set(stored);
      stored = null;
    }
    return LOCAL.get();
  }

//  （2）每个记录方法都以类对象this返回，可以使用流式编程。简洁明了，可以直接将方法名.出来
  public ErrorContext resource(String resource) {// 资源resource或者资源路径URL
    this.resource = resource;
    return this;
  }

  public ErrorContext activity(String activity) {// 正在执行的活动
    this.activity = activity;
    return this;
  }

  public ErrorContext object(String object) {// 涉及的对象
    this.object = object;
    return this;
  }

  public ErrorContext message(String message) {// 错误信息
    this.message = message;
    return this;
  }

  public ErrorContext sql(String sql) {// 执行的sql语句
    this.sql = sql;
    return this;
  }

  public ErrorContext cause(Throwable cause) {// 原因
    this.cause = cause;
    return this;
  }

  public ErrorContext reset() {// 将ErrorContext中的全部变量清空，清空ThreadLocalMap中关于currentThread的资源
    resource = null;
    activity = null;
    object = null;
    message = null;
    sql = null;
    cause = null;
    LOCAL.remove();
    return this;
  }

  @Override
  public String toString() {
    StringBuilder description = new StringBuilder();

    // message
    if (this.message != null) {
      description.append(LINE_SEPARATOR);
      description.append("### ");
      description.append(this.message);
    }

    // resource
    if (resource != null) {
      description.append(LINE_SEPARATOR);
      description.append("### The error may exist in ");
      description.append(resource);
    }

    // object
    if (object != null) {
      description.append(LINE_SEPARATOR);
      description.append("### The error may involve ");
      description.append(object);
    }

    // activity
    if (activity != null) {
      description.append(LINE_SEPARATOR);
      description.append("### The error occurred while ");
      description.append(activity);
    }

    // activity
    if (sql != null) {
      description.append(LINE_SEPARATOR);
      description.append("### SQL: ");
      description.append(sql.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ').trim());
    }

    // cause
    if (cause != null) {
      description.append(LINE_SEPARATOR);
      description.append("### Cause: ");
      description.append(cause.toString());
    }

    return description.toString();
  }

}
